package gocache

import (
	"fmt"
	"gocache/consistenthash"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"strings"
	"sync"
)

const (
	_defaultBasePath = "/_gocache/"
	_defaultReplicas = 50
)

// 创建结构体HTTPPool，作为承载节点间HTTP通信的核心数据结构
type HTTPPool struct {
	self     string // 记录自己的地址，包括主机名/IP 端口
	basePath string // 节点间通信地址的前缀 默认在前面定义
	// 一个主机上还可能承载其他的服务，加一段Path是好习惯
	peers       *consistenthash.Map // 根据具体的key选择节点
	mu          sync.Mutex
	httpGetters map[string]*httpGetter // 映射远程节点与具体的httpGetter。每个远程节点对应一个httpGetter，因为httpGetter与远程节点的地址baseURL有关
}

type httpGetter struct {
	baseURL string // 将要访问的远程节点的地址
}

func NewHTTPPool(self string) *HTTPPool {
	return &HTTPPool{
		self:     self,
		basePath: _defaultBasePath,
	}
}

// Log 将通信的地址打印到日志中
func (p *HTTPPool) Log(format string, v ...interface{}) {
	log.Printf("[Server %s] %s", p.self, fmt.Sprintf(format, v...))
}

// ServeHTTP
func (p *HTTPPool) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if !strings.HasPrefix(r.URL.Path, p.basePath) { // 如果当前请求的路径不是以basePath开头 直接panic
		panic("HTTP servering unexpected path:" + r.URL.Path)
	}
	// 调用log方法将请求的method和path打印到日志中
	p.Log("%s %s", r.Method, r.URL.Path)
	// 约定访问格式为/<basepath>/<groupname>/<key>
	// 将请求路径中默认路径切分掉，剩下的路径按照 / 切分成两部分，获取每一部分的字符串
	parts := strings.SplitN(r.URL.Path[len(p.basePath):], "/", 2)
	if len(parts) != 2 {
		http.Error(w, "bad request", http.StatusBadRequest)
		return
	}
	groupName := parts[0]
	key := parts[1]
	// 通过groupName得到group实例
	group := GetGroup(groupName)
	// 如果groups中没有groupName对应的缓存组，则返回错误
	if group == nil {
		http.Error(w, "no such group"+groupName, http.StatusBadRequest)
		return
	}
	// 取到缓存数据
	view, err := group.Get(key)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	w.Header().Set("Content-Type", "application/octet-stream")
	// 将缓存值作为响应体返回给客户端
	w.Write(view.ByteSlice())
}

// Get 获取返回值 并转换陈[]byte类型
func (h *httpGetter) Get(group, key string) ([]byte, error) {
	// 将完整路径拼接起来
	u := fmt.Sprintf("%v%v/%v", h.baseURL, url.QueryEscape(group), url.QueryEscape(key))
	resp, err := http.Get(u)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("server returned:%v", resp.Status)
	}
	bytes, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("reading response body:%v", err)
	}
	return bytes, nil
}

var _peerGetter = (*httpGetter)(nil)

// Set 实例化一致性哈希算法，并且添加了传入的节点
func (p *HTTPPool) Set(peers ...string) {
	p.mu.Lock()
	defer p.mu.Unlock()
	p.peers = consistenthash.New(_defaultReplicas, nil)
	p.peers.Add(peers...)
	p.httpGetters = make(map[string]*httpGetter, len(peers))
	for _, peer := range peers {
		// 为每个节点创建一个http客户端httpGetter
		p.httpGetters[peer] = &httpGetter{baseURL: peer + p.basePath}
	}
}

// PickPeer 包装了一致性哈希算法的Get方法 根据具体的key选择对应的节点并返回http客户端
func (p *HTTPPool) PickPeer(key string) (PeerGetter, bool) {
	p.mu.Lock()
	defer p.mu.Unlock()
	if peer := p.peers.Get(key); peer != "" && peer != p.self {
		p.Log("pick peer %s", peer)
		return p.httpGetters[peer], true
	}
	return nil, false
}

var _PeerPicker = (*HTTPPool)(nil)

// 至此 HTTPPool能提供HTTP服务 也能根据具体的key 创建http客户端从远程节点获取缓存值
